#include "cacheCMP.h"
#include "../../utils/libcache/Directory.h"

static address_t nil;
static Directory dirEntryNil(1);

CacheCMP::CacheCMP(uint32_t chips,uint32_t threadsperchip,
        uint32_t threads,uint32_t lineSize,uint32_t pageSize,
        uint32_t L0Size, uint32_t L1Size,uint32_t L2Size,bool L2Shared,bool L1Prefetch)
    : CMP(threads, pageSize, lineSize, L0Size)
{
    _chips=chips;
    _threadsperchip=threadsperchip;
    _L1Size=L1Size;
    _L2Size=L2Size;
    _L2Shared=L2Shared;
    _L1Prefetch=L1Prefetch;


    for (int i=0;i<_threads;i++) {
        _L1Cache[i] =  new CacheWriteBack <address_t>  (_L1Size,4,_lineSize,false,0);
    }
    /* L1 cache stats */
    _L1CacheHits = new uint32_t [threads];
    _L1CacheHitInvalidate = new uint32_t [threads];
    _L1CacheRemoteHits = new uint32_t [threads];
    _L1CacheMisses = new uint32_t [threads];
    _L1CacheReplacements = new uint32_t [threads];
    _L1CacheEvictions = new uint32_t [threads];

    if (_L2Size>0) {
        if (_L2Shared) {
            _L2Cache[0] =  new CacheWriteBack <address_t>  (_L2Size,4,_lineSize,false,0);
        }
        else {
            for (int i=0;i<_threads;i++) {
                _L2Cache[i] =  new CacheWriteBack <address_t>  (_L2Size,4,_lineSize,false,0);
            }
        }
        /* L2 cache stats */
        _L2CacheHits = new uint32_t [threads];
        _L2CacheHitInvalidate = new uint32_t [threads];
        _L2CacheAccesses = new uint32_t [threads];
        _L2CacheRemoteAccesses = new uint32_t [threads];
        _L2CacheRemoteHitsOnChip = new uint32_t [threads];
        _L2CacheRemoteHitsOffChip = new uint32_t [threads];
        _L2CacheMisses = new uint32_t [threads];
        _L2CacheReplacements = new uint32_t [threads];
        _L2CacheWriteBacks = new uint32_t [threads];
    }
    reset();
}

CacheCMP::~CacheCMP()
{
    for (uint32_t i=0;i<_threads;i++) {
        delete _L1Cache[i];
    }

    delete [] _L1CacheHits;
    delete [] _L1CacheHitInvalidate;
    delete [] _L1CacheRemoteHits;
    delete [] _L1CacheMisses;
    delete [] _L1CacheReplacements;
    delete [] _L1CacheEvictions;

    if (_L2Size>0) {
        if (_L2Shared) {
            delete _L2Cache[0];
        }
        else {
            for (uint32_t i=0;i<_threads;i++) {
                delete _L2Cache[i];
            }
        }

        delete [] _L2CacheHits;
        delete [] _L2CacheHitInvalidate;
        delete [] _L2CacheAccesses;
        delete [] _L2CacheRemoteAccesses;
        delete [] _L2CacheRemoteHitsOnChip;
        delete [] _L2CacheRemoteHitsOffChip;
        delete [] _L2CacheMisses;
        delete [] _L2CacheReplacements;
        delete [] _L2CacheWriteBacks;
    }
}

    void
CacheCMP::reset()
{
    CMP::reset();

    memset(_L1CacheHits,0,sizeof(uint32_t)*_threads);
    memset(_L1CacheHitInvalidate,0,sizeof(uint32_t)*_threads);
    memset(_L1CacheRemoteHits,0,sizeof(uint32_t)*_threads);
    memset(_L1CacheMisses,0,sizeof(uint32_t)*_threads);
    memset(_L1CacheReplacements,0,sizeof(uint32_t)*_threads);
    memset(_L1CacheEvictions,0,sizeof(uint32_t)*_threads);
    if (_L2Size>0) {
        memset(_L2CacheHits,0,sizeof(uint32_t)*_threads);
        memset(_L2CacheHitInvalidate,0,sizeof(uint32_t)*_threads);
        memset(_L2CacheAccesses,0,sizeof(uint32_t)*_threads);
        memset(_L2CacheRemoteAccesses,0,sizeof(uint32_t)*_threads);
        memset(_L2CacheRemoteHitsOnChip,0,sizeof(uint32_t)*_threads);
        memset(_L2CacheRemoteHitsOffChip,0,sizeof(uint32_t)*_threads);
        memset(_L2CacheMisses,0,sizeof(uint32_t)*_threads);
        memset(_L2CacheReplacements,0,sizeof(uint32_t)*_threads);
        memset(_L2CacheWriteBacks,0,sizeof(uint32_t)*_threads);
    }

}

void
CacheCMP::updateL1(address_t addr,uint32_t thread)
{
    address_t replacedTag=0;

    if (_L1Cache[thread]->read(addr,replacedTag,dirEntryNil,dirEntryNil,nil,thread)) {
        _L1CacheReplacements[thread]++;
    }
}

void
CacheCMP::updateL2(address_t addr,uint32_t thread)
{
    address_t currentAddr=0;

    if (_L2Cache[thread]->read(addr,currentAddr,dirEntryNil,dirEntryNil,nil,thread)) {
        _L2CacheReplacements[thread]++;
        if (currentAddr!=0) //isValid
            _L1Cache[thread]->invalidate(currentAddr);
    }
}

bool
CacheCMP::isLocalChipLocation(uint32_t location,uint32_t thread)
{
       return (location/_threadsperchip)==(thread/_threadsperchip);
}

void
CacheCMP::dirtyL2(address_t addr,uint32_t thread)
{
    if (!_L2Shared) {
        // dirty line means invalidates rest of L2,L1
        for (int i=0;i<_threads;i++) {
            if (i!=thread) {
                if (!isLocalChipLocation(i,thread) && _chips>1)
                    _L2CacheRemoteAccesses[i]++;
                if (_L2Cache[i]->invalidate(addr)) {
                    if (_L1Cache[i]->invalidate(addr)) {
                        _L1CacheEvictions[i]++;
                        CMP::flushL0(i);
                    }
                }
            }
        }
    }
    else {
        //_L2Cache[0]->write(addr);
        for (int i=0;i<_threads;i++) {
            if (_L1Cache[i]->invalidate(addr)) {
                _L1CacheEvictions[i]++;
                CMP::flushL0(i);
            }
        }
    }
}

/* non-optimal version of CacheCMP search */
int
CacheCMP::makeMemoryAccess(Memory * mem,uint32_t accessType,address_t addr,
        uint32_t thread)
{
    address_t physical;
    address_t evict;


    if (_L0Size>0) {
        if (_L0Cache[thread]->read(addr,evict,dirEntryNil,dirEntryNil,nil,thread)) {
            _L0CacheHits[thread]++;
            return L0CACHE;
        }
        _L0CacheMisses[thread]++;
    }

    // Get Location and Access Data
    uint32_t ret = _tlb[thread]->translate(addr,physical);
    if (ret==TLB_MISS) {
        // we use the same translation
        _tlb[thread]->insert(addr,addr);
        return  UNDEFINED_LOCATION;
    }
    addr = (physical.get()/_pageSize)*_pageSize + (addr.get()%_pageSize);

    if (_L2Shared)
        return accessL2Shared(mem,accessType,addr,thread);
    else
        return accessMultipleL2(accessType,addr,thread);

}

void CacheCMP::L1Prefetcher(uint32_t accessType,address_t addr,uint32_t thread)
{
    // Simplest next_address
    if (_L1Prefetch>0) {
        addr=addr+1;
        if (accessL1(accessType,addr,thread))
            return;
        else
            accessL2(accessType,addr,thread);
    }
}

bool CacheCMP::accessL1(uint32_t accessType,address_t addr,uint32_t thread)
{
    address_t evict;
    CacheAccess accessResponse;

    if (accessType==LOAD_INST)
        accessResponse=_L1Cache[thread]->read2(addr,evict,dirEntryNil,dirEntryNil,nil,thread);
    else
        accessResponse=_L1Cache[thread]->write(addr,thread,nil);

    if (CacheAccessIsHit(accessResponse)) {
        _L1CacheHits[thread]++;
        // Fill L0
        if (accessType==LOAD_INST) {
            if (_L0Size>0) {
                _L0Cache[thread]->read(addr,evict,dirEntryNil,dirEntryNil,nil,thread);
            }
        }
        if (accessType==STORE_INST) {
            if (_L2Size>0)  dirtyL2(addr,thread);
        }
        return true;
    }
    if (CacheAccessIsHitInvalidate(accessResponse)) {
        _L1CacheHitInvalidate[thread]++;
    }
    if (CacheAccessIsEvict(accessResponse)) {
        // After EuroPar
        //if (evict!=0 && _L2Size>0) {
        //    _L2Cache[thread]->flush(evict);
        //}
    }
    _L1CacheMisses[thread]++;
    return false;
}

bool CacheCMP::accessL2(uint32_t accessType,address_t addr,uint32_t thread)
{
    address_t evict;
    CacheAccess accessResponse;

    if (_L2Size>0) {
        _L2CacheAccesses[thread]++;
        if (accessType==LOAD_INST)
            accessResponse=_L2Cache[thread]->read2(addr,evict,dirEntryNil,dirEntryNil,nil,thread);
        else
            accessResponse=_L2Cache[thread]->write(addr,thread,nil);

        if (CacheAccessIsHit(accessResponse)) {
            _L2CacheHits[thread]++;
            updateL1(addr,thread);
            if (accessType==STORE_INST) {
                CMP::flushL0(thread);
                dirtyL2(addr,thread);
            }
            return true;
        }
        if (CacheAccessIsHitInvalidate(accessResponse)) {
            _L2CacheHitInvalidate[thread]++;
        }

        _L2CacheMisses[thread]++;
    }
    return false;
}

bool
CacheCMP::accessRemoteL2(uint32_t accessType,address_t addr,uint32_t thread)
{
    address_t evict;
    CacheAccess accessResponse;

    // REMOTE L2 HIT
    for (int i=0;i<_threads;i++) {
        if (i!=thread) {
            _L2CacheRemoteAccesses[i]++;
            if (_L2Cache[i]->find(addr)) {
                if (isLocalChipLocation(i,thread))
                    _L2CacheRemoteHitsOnChip[i]++;
                else
                    _L2CacheRemoteHitsOffChip[i]++;

                if (accessType==LOAD_INST) {
                    accessResponse=_L2Cache[i]->read2(addr,evict,dirEntryNil,dirEntryNil,nil,thread);
                }
                else {
                    accessResponse=_L2Cache[i]->write(addr,thread,nil);
                    dirtyL2(addr,i);
                }
                return true;
            }
        }
    }
    return false;
}

    int
CacheCMP::accessMultipleL2(uint32_t accessType,address_t addr,uint32_t thread)
{
    bool result=false;

    if (accessL1(accessType,addr,thread))
        result=L1_HIT;
    else {
        if (accessL2(accessType,addr,thread))
            result=L2_HIT;
        else {
            if (accessRemoteL2(accessType,addr,thread))
                result=L2REMOTE_HIT;
            else {// main memory
                _mainMemoryAccesses[thread]++;
                result=MAINMEMORY_HIT;
            }
        }
        //Prefetch
        L1Prefetcher(accessType,addr,thread);
    }
    return result;
}

    int
CacheCMP::accessL2Shared(Memory * mem,uint32_t accessType,address_t addr,uint32_t thread)
{
    address_t evict;
    CacheAccess accessResponse;

    if (accessType==LOAD_INST) {
        // HIT L1
        accessResponse=_L1Cache[thread]->read2(addr,evict,dirEntryNil,dirEntryNil,nil,thread);
        if (CacheAccessIsHit(accessResponse)) {
            _L1CacheHits[thread]++;
            return L1_HIT;
        }
        if (CacheAccessIsHitInvalidate(accessResponse)) {
            _L1CacheHitInvalidate[thread]++;
        }
        if (CacheAccessIsEvict(accessResponse)) {
            // After EUROPAR
            //if (evict!=0)
            //    _L2Cache[0]->flush(evict);
        }

        _L1CacheMisses[thread]++;
        _L2CacheAccesses[0]++;
        accessResponse=_L2Cache[0]->read2(addr,evict,dirEntryNil,dirEntryNil,nil,thread);
        if (CacheAccessIsHit(accessResponse)) {
            _L2CacheHits[0]++;
            // error
            //updateL1(addr,thread);
            return L2_HIT;
        }
        if (CacheAccessIsHitInvalidate(accessResponse)) {
            _L2CacheHitInvalidate[thread]++;
        }

        _L2CacheMisses[0]++;
        // main memory
        _mainMemoryAccesses[thread]++;
        return MAINMEMORY_HIT;
    }
    else { // STORE
        // HIT L1
        accessResponse=_L1Cache[thread]->write(addr,thread,nil);
        if (CacheAccessIsHit(accessResponse)) {
            _L1CacheHits[thread]++;
            dirtyL2(addr,thread);
            return L1_HIT;
        }
        if (CacheAccessIsHitInvalidate(accessResponse)) {
            _L1CacheHitInvalidate[thread]++;
        }

        _L1CacheMisses[thread]++;
        _L2CacheAccesses[0]++;
        accessResponse=_L2Cache[0]->write(addr,thread,nil);
        if (CacheAccessIsHit(accessResponse)) {
            _L2CacheHits[0]++;
            dirtyL2(addr,0);
            // no write allocate
            return L2_HIT;
        }
        if (CacheAccessIsHitInvalidate(accessResponse)) {
            _L2CacheHitInvalidate[thread]++;
        }
        _L2CacheMisses[0]++;
        // main memory
        // no write allocate
        _mainMemoryAccesses[thread]++;
        return MAINMEMORY_HIT;

    }
    _mainMemoryAccesses[thread]++;
    return MAINMEMORY_HIT;
}

void
CacheCMP::executeCacheSimulation(Memory *mem,Trace *trace)
{
    uint64_t address=0;
    uint32_t opcode=0;
    uint32_t access[32];
    uint32_t tidFinished=0;

    memset(access, 0, sizeof(uint32_t) * _threads);

    while (tidFinished < _threads) {
      tidFinished=0;
      // one Access per Thread
      for (int tid = 0; tid < _threads; tid++ ) {
          if (trace->getMemoryAccess(tid,access[tid],address,opcode)) {
                if (makeMemoryAccess(mem,opcode,address,tid)!=UNDEFINED_LOCATION) {
                    access[tid]++;
                }
          }
          else {
              tidFinished++;
          }
      }
  }
  std::cout << "End of cache CMP simulation"<<std::endl;
}

void
CacheCMP::accessL0(address_t addr)
{
    address_t evict;
    Directory dir(1);
    /*
        if (_L0Cache[0]->read(addr,evict,dir,0,NULL)) {
            _L0CacheHits[0]++;
            return;
        }
        _L0CacheMisses[0]++;
    */
}
void
CacheCMP::executeL0Simulation(Memory *mem,Trace *trace,uint32_t tid)
{
    uint64_t address=0;
    uint32_t memOpcode=0;
    uint32_t i=0;

    cout<<"Simulate L0Execution :"<< " Threads: "<< mem->getNumThreads() << " Accesses: ";

    while (trace->getMemoryAccess(tid,i,address,memOpcode)) {
        this->accessL0(address);
        if (i%100000==0)
            std::cout <<"Access:"<<i<<endl;
        i++;
    }
}
